<twoyao>

April 20, 2016

问题的起因是在阅读java8的源码中发现BaseStream的申明

public interface BaseStream<T, S extends BaseStream<T, S>> {...}

其中S extends BaseStream<T, S> 相当隐晦,不知道啥深意。Google之后豁然开朗,又涨姿势。

要理解上面这段代码可以先从简单的情景出发,先要理解它想达到什么效果。比如说我们有下面的两个类:

public class Foo {
    Foo doSomething() { 
        // ...
        return this;
    }
}

public class Bar extends Foo {
    Bar doOtherthing() {
        // ...
        return this;
    }
} 

如果想实现链式调用,new Bar().doSomething().doOtherthing()会报编译错误,因为doSomething返回的是Foo对象而不含有doOtherthing这个成员函数。解决方法是继承重写:

public class Bar extends Foo {
    Bar doOtherthing() {
        // ...
        return this;
    }
    
    @Override
    Bar doSomething() {
        super.doSomething();
        return this;
    }
} 

然而,如果Foo有很多个链式函数,那Bar也必须全部重写,这显然不好。可以改写下Foo,让doSomething返回根据模板参数确定的类型T,然后Bar继承Foo。这样doSomething知道它应该返回什么类型。

public class Foo<T> {
    Foo doSomething() { 
        // ...
        return (T) this;
    }
}

public class Bar extends Foo<Bar> {
    Bar doOtherthing() {
        // ...
        return this;
    }
} 

但是这么做没有检查T的类型,T应该必须是Foo的子类,而不能是任意的类型。可以给它加上类型限制:

public class Foo<T extends Foo<T>> {
    Foo doSomething() { 
        // ...
        return (T) this;
    }
}

注意到T extends Foo<T>Bar extends Foo<Bar> 形式是一致的,所以Bar满足了要求。 这个例子其实就是BaseStreamS extends BaseStream<T, S>的简化(去除了T类型)。总结来说 那段代码是为了约束S必须是BaseStream的子类。

这种模式很早就有人发现了,称为Curiously recurring template pattern 除了在链式调用中的应用外,还有其他有意思的应用,这里就不展开说了。